#include <Engine/StdAfx.h>
#include <Engine/Misc/ColladaImporter.h>
#include <Engine/IMeshObject.h>
#include <Engine/Renderer/RendererStructs.h>
#include <CommonLib/MurmurHash.h>
#include <COLLADASaxFWLLoader.h>
#include <COLLADAFW.h>

namespace fastbird
{

IColladaImporter* IColladaImporter::CreateColladaImporter()
{
	return new ColladaImporter;
}

ColladaImporter::ColladaImporter()
	:mSwapYZ(false)
	,mOppositeCull(true)
{

}

ColladaImporter::~ColladaImporter()
{
	mMeshObject = 0;
}

bool ColladaImporter::ImportCollada(const char* filepath, bool yzSwap, bool oppositeCull)
{
	bool successful = false;
	mSwapYZ = yzSwap;
	mOppositeCull = oppositeCull;
	mMeshObject = IMeshObject::CreateMeshObject();
	mMeshObject->StartModification();
	if (strcmp(filepath, mFilepath.c_str()))
	{
		// different file.
		mFilepath = filepath;
		COLLADASaxFWL::Loader loader;
		successful = loader.loadDocument(filepath, this);
	}
	else
	{
		FeedGeometry();
		successful = true;
	}
	mMeshObject->EndModification(false);

	return successful;
}

void ColladaImporter::cancel(const COLLADAFW::String& errorMessage)
{
}

void ColladaImporter::start()
{
}

void ColladaImporter::finish()
{
}

bool ColladaImporter::writeGlobalAsset ( const COLLADAFW::FileInfo* asset )
{
	return true;
}

bool ColladaImporter::writeScene ( const COLLADAFW::Scene* scene )
{
	return true;
}

bool ColladaImporter::writeVisualScene ( const COLLADAFW::VisualScene* visualScene )
{
	return true;
}

bool ColladaImporter::writeLibraryNodes ( const COLLADAFW::LibraryNodes* libraryNodes )
{
	return true;
}

bool ColladaImporter::writeGeometry ( const COLLADAFW::Geometry* geometry )
{
	using namespace COLLADAFW;
	Geometry::GeometryType type = geometry->getType();
	if (type == Geometry::GEO_TYPE_MESH || type == Geometry::GEO_TYPE_CONVEX_MESH)
	{
		Mesh* pColladaMesh = dynamic_cast<Mesh*>( const_cast<Geometry*>(geometry) );
		if (pColladaMesh)
		{
			CopyData(pColladaMesh);
			FeedGeometry();
		}
	}
	return true;
}

bool ColladaImporter::writeMaterial( const COLLADAFW::Material* material )
{
	return true;
}

bool ColladaImporter::writeEffect( const COLLADAFW::Effect* effect )
{
	return true;
}

bool ColladaImporter::writeCamera( const COLLADAFW::Camera* camera )
{
	return true;
}

bool ColladaImporter::writeImage( const COLLADAFW::Image* image )
{
	return true;
}

bool ColladaImporter::writeLight( const COLLADAFW::Light* light )
{
	return true;
}

bool ColladaImporter::writeAnimation( const COLLADAFW::Animation* animation )
{
	return true;
}

bool ColladaImporter::writeAnimationList( const COLLADAFW::AnimationList* animationList )
{
	return true;
}

bool ColladaImporter::writeSkinControllerData( const COLLADAFW::SkinControllerData* skinControllerData )
{
	return true;
}

bool ColladaImporter::writeController( const COLLADAFW::Controller* controller )
{
	return true;
}

bool ColladaImporter::writeFormulas( const COLLADAFW::Formulas* formulas )
{
	return true;
}

bool ColladaImporter::writeKinematicsScene( const COLLADAFW::KinematicsScene* kinematicsScene )
{
	return true;
}

void ColladaImporter::CopyData(COLLADAFW::Mesh* pColladaMesh)
{
	using namespace COLLADAFW;
	// positions
	MeshVertexData& positions = pColladaMesh->getPositions();
	if (positions.getType() == FloatOrDoubleArray::DATA_TYPE_FLOAT)
	{
		FloatArray* pFloatArray = positions.getFloatValues();
		size_t numFloats = pFloatArray->getCount();
		mPos.clear();
		mPos.reserve(numFloats);
		mPos.assign(pFloatArray->getData(), pFloatArray->getData()+numFloats);
	}
	else if (positions.getType() == FloatOrDoubleArray::DATA_TYPE_DOUBLE)
	{
		DoubleArray* pDoubleArray = positions.getDoubleValues();
		size_t numDoubles = pDoubleArray->getCount();
		mPos.clear();
		mPos.reserve(numDoubles);
		for (size_t i=0; i<numDoubles; i++)
		{
			mPos.push_back( (float)((*pDoubleArray)[i]) );
		}
	}

	//normals

	MeshVertexData& normals = pColladaMesh->getNormals();
	if (normals.getType() == FloatOrDoubleArray::DATA_TYPE_FLOAT)
	{
		FloatArray* pFloatArray = normals.getFloatValues();
		size_t numFloats = pFloatArray->getCount();
		mNormals.clear();
		mNormals.reserve(numFloats);
		mNormals.assign(pFloatArray->getData(), pFloatArray->getData()+numFloats);
	}
	else if (normals.getType() == FloatOrDoubleArray::DATA_TYPE_DOUBLE)
	{
		DoubleArray* pDoubleArray = normals.getDoubleValues();
		size_t numDoubles = pDoubleArray->getCount();
		mNormals.clear();
		mNormals.reserve(numDoubles);
		for (size_t i=0; i<numDoubles; i++)
		{
			mNormals.push_back( (float)((*pDoubleArray)[i]) );
		}
	}

	MeshPrimitiveArray& meshPrimitives = pColladaMesh->getMeshPrimitives();
	size_t pc = meshPrimitives.getCount();
	for (size_t i=0; i<pc; i++)
	{
		mPosIndices.push_back(INDICES());
		mNormalIndices.push_back(INDICES());
		INDICES& posIndices = mPosIndices.back();
		INDICES& normalIndices = mNormalIndices.back();
		MeshPrimitive::PrimitiveType type = meshPrimitives[i]->getPrimitiveType();
		switch(type)
		{
		case MeshPrimitive::POLYGONS:
		case MeshPrimitive::POLYLIST:
		case MeshPrimitive::TRIANGLES:
			{					
				UIntValuesArray& pi =  meshPrimitives[i]->getPositionIndices();
				UIntValuesArray& ni =  meshPrimitives[i]->getNormalIndices();
				size_t ic = pi.getCount();
				size_t ic_n = ni.getCount();
				assert(ic==ic_n);
				posIndices.assign(pi.getData(), pi.getData() + ic);
				normalIndices.assign(ni.getData(), ni.getData()+ic);
			}				
			break;
		default:
			assert(0 && "Not Implemented");
			Error("Cannot handle MeshPrimitive type : %d", type);
		}
	}

}

//---------------------------------------------------------------------------
class vertex_hash : public std::unary_function<DEFAULT_INPUTS::V_PNT, size_t>
{	// hash functor
public:
	size_t operator()(const DEFAULT_INPUTS::V_PNT& data) const
	{	
		return murmur3_32((char*)&data, sizeof(data), murmurSeed);
	}
};

//---------------------------------------------------------------------------
void ColladaImporter::FeedGeometry()
{
	if (mPosIndices.empty())
		return;

	unsigned elemOffset[] = {0, 1, 2};
	if (mSwapYZ)
	{
		std::swap(elemOffset[1], elemOffset[2]);
	}
	unsigned indexOffset[] = {0, 1, 2};
	if (mOppositeCull)
	{
		std::swap(indexOffset[1], indexOffset[2]);
	}

	const INDICES& posIndices = mPosIndices[0];
	const INDICES& norIndices = mNormalIndices[0];

	size_t nextIdx = 0;
	std::set<DEFAULT_INPUTS::V_PNT> vertSet;
	std::map<DEFAULT_INPUTS::V_PNT, size_t> vertToIdx;
	assert(posIndices.size()==norIndices.size());
	size_t numIndices = posIndices.size();
	assert(numIndices%3==0);

	INDICES indices;
	indices.reserve(numIndices);
	indices.reserve(numIndices);
	std::vector<Vec3> positions;
	positions.reserve(numIndices);
	std::vector<Vec3> normals;
	normals.reserve(numIndices);
	std::vector<Vec2> uvs;
	uvs.reserve(numIndices);

	for(size_t i=0; i<numIndices; i+=3)
	{
		for (int k=0; k<3; k++)
		{
			size_t pi = posIndices[i+indexOffset[k]]*3;
			size_t ni = norIndices[i+indexOffset[k]]*3;
			DEFAULT_INPUTS::V_PNT vert(
				Vec3(mPos[pi], mPos[pi+elemOffset[1]], mPos[pi+elemOffset[2]]),
				Vec3(mNormals[ni], mNormals[ni+elemOffset[1]], mNormals[ni+elemOffset[2]]),
				Vec2(0.0f, 0.0f));
			auto ret = vertSet.insert(vert);
			if (ret.second)
			{
				// new vertex
				vertToIdx[vert] = nextIdx;
				indices.push_back(nextIdx++);
				positions.push_back(vert.p);
				normals.push_back(vert.n);
				uvs.push_back(vert.uv);
			}
			else
			{
				assert(vertToIdx.find(vert) != vertToIdx.end());
				// existing
				indices.push_back(vertToIdx[vert]);
			}
		}
	}
	bool b32bit = indices.size() > std::numeric_limits<USHORT>::max();
	SmartPtr<IIndexBuffer> pIndexBuffer;
	if (b32bit)
	{
		if (gFBEnv)
			pIndexBuffer = gFBEnv->pRenderer->CreateIndexBuffer(&indices[0], indices.size(), INDEXBUFFER_FORMAT_32BIT);
	}
	else
	{
		std::vector<USHORT> indices16;
		indices16.reserve(indices.size());
		for each(UINT i in indices)
		{
			indices16.push_back((USHORT)i);
		}
		if (gFBEnv)
			pIndexBuffer = gFBEnv->pRenderer->CreateIndexBuffer(&indices16[0], indices16.size(), INDEXBUFFER_FORMAT_16BIT);
	}

	mMeshObject->SetIndexBuffer(pIndexBuffer);
	
	const size_t numVertices = nextIdx;
	assert(numVertices == positions.size());
	assert(numVertices == normals.size());
	assert(numVertices == uvs.size());
	mMeshObject->SetPositions(&positions[0], numVertices);
	mMeshObject->SetNormals(&normals[0], numVertices);
	mMeshObject->SetUVs(&uvs[0], numVertices);
}

}